/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.googlecode.jcsvb;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author kelvio
 */
public class Csv {
    
    private static String SEPARATOR = ",";
    private static String TEXT_DELIMITER = "\"";
    
    /**
     * @param <T>
     * @param dataSource
     * @return
     */
    public static <T> String dump(T... dataSource) throws Exception {
        return dump(Arrays.asList(dataSource));
    }

    public static <T> String dump(List<T> dataSource) throws Exception {
        StringBuilder sb = new StringBuilder();
        
        Map<Class,List<T>> values = new HashMap<>();
        for( T t : dataSource ) {
            if( values.containsKey(t.getClass()) ) {
                List list = values.get(t.getClass());
                list.add(t);
                values.put(t.getClass(), list);
            } else {
                List<T> list = new ArrayList<>();
                list.add(t);
                values.put(t.getClass(), list);
            }
        }
        List<Class> keys = new ArrayList<>();
        keys.addAll(values.keySet());
        Collections.sort( keys , new Comparator<Class>() {
            @Override
            public int compare(Class o1, Class o2) {
                return o1.getSimpleName().compareTo(o2.getSimpleName());
            }
        });
        
        if( keys.size()>1 ) {
            for( Class key : keys ) {
                List list = values.get(key);
                if( list!=null && !list.isEmpty() ) {
                    dumpList(sb, list);
                    sb.append("\n");
                }
            }
        } else {
            dumpList(sb, dataSource);
        }
        return sb.toString();
    }
    
    private static void dumpList(StringBuilder sb, List dataSource) throws Exception {
        Object tmp = dataSource.get(0);
        List<CsvColumnData> columns = getColumns(tmp);
        dumpHeader(sb, columns);
        for( Object obj : dataSource ) {
            dumpData(sb, obj, columns);
        }
    }
    
    private static List<CsvColumnData> getColumns(Class clazz) {
        List<CsvColumnData> columns = new ArrayList<>();
        do {
            Field fields[] = clazz.getDeclaredFields();
            for( Field f : fields ) {
                CsvColumn csvColumn = f.getAnnotation(CsvColumn.class);
                if( csvColumn != null ) {
                    columns.add(new CsvColumnData(csvColumn.title(), f.getName(), csvColumn.index()));
                }
            }
            clazz = clazz.getSuperclass();
        } while( clazz!=Object.class );
        // -- Ordena a lista...
        Collections.sort(columns, new Comparator<CsvColumnData>() {
            @Override
            public int compare(CsvColumnData o1, CsvColumnData o2) {
                if( o1.getIndex() < o2.getIndex() ) {
                    return -1;
                } else if(o1.getIndex() > o2.getIndex() ) {
                    return 1;
                }
                return 0;
            }
        });
        return columns;
    }
    private static List<CsvColumnData> getColumns(Object obj) {
        return getColumns(obj.getClass());
    }
    
    private static void dumpHeader(StringBuilder sb, List<CsvColumnData> columns) throws Exception {
        for( int i=0;i<columns.size(); i++ ) {
            CsvColumnData columnData = columns.get(i);
            String title = columnData.getTitle();
            if( title.isEmpty() ) {
                title = columnData.getField();
            }
            sb.append(TEXT_DELIMITER).append(title).append(TEXT_DELIMITER);
            if( i < columns.size()-1 ) {
                sb.append(SEPARATOR);
            }
        }
        sb.append("\n");
    }
    private static void dumpData(StringBuilder sb, Object obj, List<CsvColumnData> columns) throws Exception {
        for( int i=0;i<columns.size(); i++ ) {
            CsvColumnData columnData = columns.get(i);
            String value = getFieldValue(obj, columnData.getField());
            sb.append(TEXT_DELIMITER).append(value).append(TEXT_DELIMITER);
            if( i < columns.size()-1 ) {
                sb.append(SEPARATOR);
            }
        }
        sb.append("\n");
    }

    /**
     *
     * @param csvData
     * @return
     */
    public static String dump(CsvData csvData) throws Exception {
        if( csvData !=null ) {
            CsvLayout layout = csvData.getLayout();
            if( layout != null ) {
                SEPARATOR = layout.getSeparator()!=null && !layout.getSeparator().isEmpty() ? layout.getSeparator() : ",";
                TEXT_DELIMITER = layout.getTextDelimiter()!=null ? layout.getTextDelimiter() : "\"";
            }
            StringBuilder sb = new StringBuilder();
            if( layout!=null && layout.getHeaders()!=null ) {
                for( String h : layout.getHeaders() ) {
                    sb.append(h).append("\n");
                }
            }
            
            sb.append( dump(csvData.getDataSource()) );
            
            if( layout!=null && layout.getFooters()!=null ) {
                for( String f : layout.getFooters()) {
                    sb.append(f).append("\n");
                }
            }
            SEPARATOR = ",";
            TEXT_DELIMITER = "\"";
            return sb.toString();
        }
        return null;
    }

    public static <T> T loadObject(Class<T> clazz, String dataSource, String separator, String textDelimiter, int row) throws Exception {
        List<CsvColumnData> columns = getColumns(clazz);
        String lines[] = dataSource.split("\n");
        String dataObject = lines[row];
        String data[] = dataObject.split(separator);
        T t = clazz.newInstance();
        for( int i=0;i<data.length;i++ ) {
            CsvColumnData csvColumn = columns.get(i);
            setFieldValue(t, csvColumn.getField(), data[i].replace(textDelimiter, ""));
        }
        return t;
    }

    public static <T> List<T> loadObjects(Class<T> clazz, String dataSource, String separator, String textDelimiter, int ... rows) throws Exception {
        List<T> list = new ArrayList<>();
        for (int row : rows) {
            list.add(loadObject(clazz, dataSource, separator, textDelimiter, row));
        }
        return list;
    }
    
    // ------------------------------------------------------------------------- 
    private static void setFieldValue(Object obj, String fieldName, String value) {
        if (obj != null && fieldName != null) {
            try {
                Field field = obj.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                if( field.getType() == Date.class ) {
                    SimpleDateFormat sdf = new SimpleDateFormat();
                    CsvTemporalColumn csvTemporalColumn = field.getAnnotation(CsvTemporalColumn.class);
                    if( csvTemporalColumn!=null ) {
                        sdf.applyPattern(csvTemporalColumn.pattern());
                    }
                    Date date = sdf.parse(value);
                    field.set(obj, date);
                    return;
                }
                field.set(obj, value);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    private static String getFieldValue(Object obj, String fieldName) {
        if (obj != null && fieldName != null) {
            try {
                Field field = obj.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                Object value = field.get(obj);
                // -- Instance of Date...
                if( value instanceof Date ) {
                    CsvTemporalColumn csvTemporalColumn = field.getAnnotation(CsvTemporalColumn.class);
                    if( csvTemporalColumn!=null ) {
                        SimpleDateFormat sdf = new SimpleDateFormat(csvTemporalColumn.pattern());
                        return sdf.format(value);
                    }
                }
                // -- Instance of List...
                if( value!=null ) {
                    return value.toString();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "-";
    }
    // -------------------------------------------------------------------------
    private static class CsvColumnData {
        
        private String title;
        private String field;
        private int    index;

        public CsvColumnData(String title, String field, int index) {
            this.title = title;
            this.field = field;
            this.index = index;
        }

        public String getTitle() {
            return title;
        }

        public String getField() {
            return field;
        }

        public int getIndex() {
            return index;
        }
        
    }
    // -------------------------------------------------------------------------
}
